/* test_threads_func_uthread_join_1():
	A test for uthread_join(). Test the regular case + the special error cases.
	
	* Does a join to an invalid thread id (here and there and everywhere).
	* Does join from thread 2 to thread 1 (thread_return is not NULL).
	* Does join from thread 0 to 2.
	* Re-create threads 1,2.
	* Does join from thread 1 to thead 2 (thread_return is NULL).
*/


#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include "test_general.h"

#include "user_threads.h"


bool test_threads_func_uthread_join_1();

#define THREADS_NUM 3

static int thread1_start_routine(void * param) {
	TEST_ASSERT( 53 == (*((int*)param)) );


	TEST_ASSERT (uthread_self() == 1);

	some_long_operation(0.6);
	
	/* Thread 2 did 'join' to us. So it stoped junning 0.5 seconds ago.*/
	TEST_ASSERT(last_running_times[2] < last_running_times[1]-0.3);
	TEST_ASSERT(last_running_times[2] > last_running_times[1]-0.8);

	int tmp = uthread_join(3,NULL);
	TEST_ASSERT(UTHREAD_INVALID == tmp);

	thread_ok[1] = true;
	return 2491;
}

static int thread2_start_routine(void * param) {
	TEST_ASSERT( 53 == (*((int*)param)) );

	TEST_ASSERT (uthread_self() == 2);

	some_long_operation(0.1);
	
	int tmp, tmp_ret;

	tmp = uthread_join(3,NULL);
	TEST_ASSERT(UTHREAD_INVALID == tmp);

	tmp_ret = uthread_join(1, &tmp);
	TEST_ASSERT (UTHREAD_SUCCESS == tmp_ret);
	TEST_ASSERT(2491 == tmp);
	TEST_ASSERT(last_running_times[2] < last_running_times[1]-0.3);
	TEST_ASSERT(last_running_times[2] > last_running_times[1]-0.8);
	TEST_ASSERT(last_running_times[1] > get_precise_time()-0.1);

	thread_ok[2] = true;
	return -1798;
}


static int thread1_B_start_routine(void * param) {
	TEST_ASSERT( 53 == (*((int*)param)) );


	TEST_ASSERT (uthread_self() == 1);


	some_long_operation(0.1);
	
	int tmp_ret;
	tmp_ret = uthread_join(2, NULL);
	TEST_ASSERT (UTHREAD_SUCCESS == tmp_ret);
	TEST_ASSERT(last_running_times[1] < last_running_times[2]-0.3);
	TEST_ASSERT(last_running_times[1] > last_running_times[2]-0.8);
	TEST_ASSERT(last_running_times[2] > get_precise_time()-0.1);

	thread_ok[1] = true;
	return -63;
}

static int thread2_B_start_routine(void * param) {
	TEST_ASSERT( 53 == (*((int*)param)) );

	TEST_ASSERT (uthread_self() == 2);

	some_long_operation(0.6);
	
	/* Thread 1 did 'join' to us. So it stoped junning 0.5 seconds ago.*/
	TEST_ASSERT(last_running_times[1] < last_running_times[2]-0.3);
	TEST_ASSERT(last_running_times[1] > last_running_times[2]-0.8);

	thread_ok[2] = true;
	return 8;
}



bool test_threads_func_uthread_join_1()
{
	init_individual_test();

	int tmp, tmp2;

	if (initilize_uthread_in_each_test) {
		tmp = uthread_init();
		TEST_ASSERT(tmp == UTHREAD_SUCCESS);
	}


	/* Checking: UTHREAD_INVALID */

	tmp = uthread_join(-1,&tmp2);
	TEST_ASSERT(UTHREAD_INVALID == tmp);
	tmp = uthread_join(-1,NULL);
	TEST_ASSERT(UTHREAD_INVALID == tmp);
	tmp = uthread_join(1,&tmp2);
	TEST_ASSERT(UTHREAD_INVALID == tmp);
	tmp = uthread_join(1,NULL);
	TEST_ASSERT(UTHREAD_INVALID == tmp);


	thread_id threads[THREADS_NUM];

	threads[0] = uthread_self();



	int FiftyThree = 53;

	threads[1] = uthread_create(thread1_start_routine, &FiftyThree);

	threads[2] = uthread_create(thread2_start_routine, &FiftyThree);


	TEST_ASSERT(threads[0] == 0);
	TEST_ASSERT(threads[1] == 1);
	TEST_ASSERT(threads[2] == 2);

	tmp = uthread_join(threads[2],&tmp2);


	TEST_ASSERT(UTHREAD_SUCCESS == tmp);
	TEST_ASSERT(-1798 == tmp2);


	thread_ok[0] = true;
	TEST_ASSERT(are_all_threads_ok(THREADS_NUM));

	init_individual_test();
	/* Starting from fresh */

	threads[1] = uthread_create(thread1_B_start_routine, &FiftyThree);
	threads[2] = uthread_create(thread2_B_start_routine, &FiftyThree);

	TEST_ASSERT(threads[1] == 1);
	TEST_ASSERT(threads[2] == 2);

	tmp = uthread_join(threads[1],NULL);
	TEST_ASSERT(UTHREAD_SUCCESS == tmp);
	
	thread_ok[0] = true;

	TEST_ASSERT(are_all_threads_ok(THREADS_NUM));
	return true;
}
